/**
 * 
 */
package org.dragonfire.ti;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

import org.dragonfire.util.DArray;
import org.dragonfire.util.token.Token;
import org.dragonfire.util.token.TokenTable;

public class VarEntry implements TIConstants {

	/**
	 * The magic numbers that start each variable entry.<br />
	 * The TI-85 uses an offset value (number of bytes to the start of data) for
	 * it's magic number, so -1 is used as a filler. This value is ignored by
	 * the build() method, but is inserted to make the code uniform.
	 */
	private static final byte[] magicChar = new byte[] {
			11, // TI73
			11, // TI82
			11, // TI83
			13, // TI83+/TI84+
			-1, // TI85
			12
	// TI86
	};
	/**
	 * The maximum calculator model supported, in this case the TI-86
	 */
	public static final int MAX_CALC = 5;
	/**
	 * The size of the header. The total size of the entry is
	 * <code>headerSize + dataSize</code><br />
	 * Because the name for a TI-85 isn't fixed-width, the size must be added to
	 * the header size and the data size.
	 */
	private static final int[] size = new int[] {
			15, 15, 15, 17, 8, 16
	};
	/**
	 * Whether the variable should be sent to RAM or archive. Only applies to
	 * TI-83+ variables.
	 */
	private boolean archived = false;
	/**
	 * The calculator type the variable should be sent to.
	 */
	private int calc = 0;
	/**
	 * The data contents of the variable
	 */
	private Byte[] data = null;
	/**
	 * The name of the variable
	 */
	private Byte[] name = null;
	/**
	 * The variable type
	 */
	private byte type = 0;
	/**
	 * The version of the variable. Only applies to TI-83+ variables.
	 */
	private int version = 0;

	private TokenTable ttable;

	public VarEntry(int calcType, int varType, Byte[] varName, Byte[] data) {
		calc = calcType;
		type = (byte) varType;
		name = varName;
		setData(data);
		ttable = TIFile.getTokenTable(calcType);
	}

	public VarEntry(int calcType, int varType) {
		this(calcType, varType, new Byte[0], new Byte[0]);
	}

	public TokenTable getTokenTable() {
		return ttable;
	}

	public byte[] build() {
		// create an array for the data
		byte[] entry = new byte[size()];
		// if it's an 85, first two bytes hold offset to the data.
		// on the other calculators, it has a magic number.
		if (calc == TIFile.CALC_85) {
			// get length
			int tmp = 6 + name.length;
			// split into two bytes
			entry[0] = (byte) (tmp & 0xFF);
			entry[1] = (byte) ((tmp >> 8) & 0xFF);
		}
		else {
			// insert a magic character
			entry[0] = magicChar[calc];
		}
		// next two bytes are always the data length
		entry[2] = (byte) (data.length & 0xFF);
		entry[3] = (byte) ((data.length >> 8) & 0xFF);
		// 5th byte is always the variable type
		entry[4] = type;
		// next 8 bytes are for the name of the variable.
		Byte[] byteName = name;
		for (int i = 0; i < byteName.length; i++) {
			entry[i + 5] = byteName[i];
		}
		int index = 5 + byteName.length;
		switch (calc) {
			case TIFile.CALC_8X:
				entry[index++] = (byte) version;
				entry[index++] = archived ? (byte) 0x80 : 0;
			case TIFile.CALC_73:
			case TIFile.CALC_82:
			case TIFile.CALC_83:
			case TIFile.CALC_85:
			case TIFile.CALC_86:
				entry[index++] = (byte) (data.length & 0xFF);
				entry[index++] = (byte) ((data.length >> 8) & 0xFF);
				break;
		}
		for (int i = 0; i < data.length; i++) {
			entry[index++] = data[i];
		}
		return entry;
	}

	public int getCalc() {
		return calc;
	}

	public Byte[] getData() {
		return data;
	}

	public Byte[] getName() {
		return name;
	}

	public byte getType() {
		return type;
	}

	public String getDetokenizedName() {
		String name = Token.toDisplayString(getTokenTable().readTokens(getName()));
		while (name.length() > 2 && name.substring(name.length() - 3).equals("{0}")) {
			name = name.substring(0, name.length() - 3);
		}
		return name;
	}

	public String getDetokenizedEditName() {
		String name = Token.toEditString(getTokenTable().readTokens(getName()));
		while (name.length() > 2 && name.substring(name.length() - 3).equals("{0}")) {
			name = name.substring(0, name.length() - 3);
		}
		return name;
	}

	public void setName(String name) {
		setName(Token.toByteArray(getTokenTable().readTokens(name)));
	}

	public int getVersion() {
		return version;
	}

	public boolean isArchived() {
		return archived;
	}

	public void setArchived(boolean archived) {
		this.archived = archived;
	}

	/**
	 * Sets the calculator model that this variable should be sent to.
	 * 
	 * @param calcModel
	 */
	public void setCalc(int calcModel) {
		if (calcModel > MAX_CALC || calcModel < 0)
			throw new IllegalArgumentException("Invalid Calc ID: " + calcModel);
		calc = calcModel;
	}

	/**
	 * Sets the data contents of this <code>VarEntry</code>. Subclasses of
	 * <code>VarEntry</code> should override this method and perform validity
	 * checks on the data being inserted.
	 * 
	 * @param data
	 *            The data this <code>VarEntry</code> should contain
	 */
	public void setData(Byte[] data) {
		this.data = data;
	}

	public void setName(Byte[] name) {
		this.name = name;
	}

	public void setType(byte type) {
		this.type = type;
	}

	public void setVersion(int version) {
		this.version = version;
	}

	public int size() {
		return size[calc] + data.length + (calc == TIFile.CALC_85 ? name.length : 0);
	}

	public InputStream dataAsInputStream() {
		return new ByteArrayInputStream(DArray.byteArray(getData()));
	}

	public static VarEntry createEntry(int calcType, int varType, Byte[] name, Byte[] data) {
		VarEntry ve;
		switch (varType) {
			case TYPE_PROGRAM:
			case TYPE_PROTECTED_PROGRAM:
				ve = new TIProgram(calcType, varType, name, data);
				break;
			default:
				ve = new VarEntry(calcType, varType, name, data);
				break;
		}
		return ve;
	}
}